import Callout from 'components/Callout';
import Details from 'components/Details';
import {Tabs, Tab} from 'nextra-theme-docs';

# Server Actions, Metadata & Route Handlers

There are a few places in Next.js apps where you can apply internationalization outside of React components:

1. [Server Actions](https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations)
2. [Metadata API](https://nextjs.org/docs/app/building-your-application/optimizing/metadata)
3. [Open Graph images](https://nextjs.org/docs/app/api-reference/file-conventions/metadata/opengraph-image)
4. [Manifest](https://nextjs.org/docs/app/api-reference/file-conventions/metadata/manifest)
5. [Sitemap](https://nextjs.org/docs/app/api-reference/file-conventions/metadata/sitemap)
6. [Route Handlers](https://nextjs.org/docs/app/building-your-application/routing/router-handlers)

`next-intl/server` provides a set of [awaitable functions](/docs/environments/server-client-components#async-components) that can be used in these cases.

### Server Actions

[Server Actions](https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations) provide a mechanism to execute server-side code that is invoked by the client. In case you're returning user-facing messages, you can use `next-intl` to localize them based on the user's locale.

```tsx
import {getTranslations} from 'next-intl/server';

async function loginAction(data: FormData) {
  'use server';

  const t = await getTranslations('LoginForm');
  const areCredentialsValid = /* ... */;
  if (!areCredentialsValid) {
    return {error: t('invalidCredentials')};
  }
}
```

Note that when you're displaying messages generated in Server Actions to the user, you should consider the case if the user can switch the locale while the message is displayed to ensure that the UI is localized consistently. If you're using [a `[locale]` segment](/docs/getting-started/app-router) as part of your routing strategy then this is handled automatically. If you're not, you might want to clear the message manually, e.g. by [resetting the state](https://react.dev/learn/preserving-and-resetting-state#resetting-a-form-with-a-key) of the respective component via `key={locale}`.

<Details id="server-actions-zod">
<summary>When using Zod for validation, how can I localize error messages?</summary>

[Zod](https://zod.dev/) allows you to provide [contextual error maps](https://zod.dev/ERROR_HANDLING?id=contextual-error-map) that can be used to customize error messages per parse invocation. Since the locale is specific to a particular request, this mechanism comes in handy to turn structured errors from `zod` into localized messages:

```tsx
import {getTranslations} from 'next-intl/server';
import {loginUser} from '@/services/session';
import {isEqual} from 'lodash';
import {z} from 'zod';

const loginFormSchema = z.object({
  email: z.string().email(),
  password: z.string().min(1)
});

// ...

async function loginAction(data: FormData) {
  'use server';

  const t = await getTranslations('LoginForm');
  const values = Object.fromEntries(data);

  const result = await loginFormSchema
    .refine(async (credentials) => loginUser(credentials), {
      message: t('invalidCredentials')
    })
    .safeParseAsync(values, {
      errorMap(issue, ctx) {
        let message;

        if (isEqual(issue.path, ['email'])) {
          message = t('invalidEmail');
        } else if (isEqual(issue.path, ['password'])) {
          message = t('invalidPassword');
        }

        return {message: message || ctx.defaultError};
      }
    });

  // ...
}
```

See the [App Router without i18n routing example](/examples#app-router-without-i18n-routing) for a working implementation.

</Details>

### Metadata API

To internationalize metadata like the page title, you can use functionality from `next-intl` in the [`generateMetadata`](https://nextjs.org/docs/app/api-reference/functions/generate-metadata#generatemetadata-function) function that can be exported from pages and layouts.

```tsx filename="layout.tsx"
import {getTranslations} from 'next-intl/server';

export async function generateMetadata({params: {locale}}) {
  const t = await getTranslations({locale, namespace: 'Metadata'});

  return {
    title: t('title')
  };
}
```

<Callout>
  By passing an explicit `locale` to the awaitable functions from `next-intl`,
  you can make the metadata handler eligible for [static
  rendering](/docs/getting-started/app-router/with-i18n-routing#static-rendering)
  if you're using [i18n routing](/docs/getting-started/app-router).
</Callout>

### Open Graph images

If you're programmatically generating [Open Graph images](https://nextjs.org/docs/app/api-reference/file-conventions/metadata/opengraph-image), you can apply internationalization by calling functions from `next-intl` in the exported function:

```tsx filename="opengraph-image.tsx"
import {ImageResponse} from 'next/og';
import {getTranslations} from 'next-intl/server';

export default async function OpenGraphImage({params: {locale}}) {
  const t = await getTranslations({locale, namespace: 'OpenGraphImage'});
  return new ImageResponse(<div style={{fontSize: 128}}>{t('title')}</div>);
}
```

### Manifest

Since [the manifest file](https://nextjs.org/docs/app/api-reference/file-conventions/metadata/manifest) needs to be placed in the root of the `app` folder (outside the `[locale]` dynamic segment), you need to provide a locale explicitly since `next-intl` can't infer it from the pathname:

```tsx filename="app/manifest.ts"
import {MetadataRoute} from 'next';
import {getTranslations} from 'next-intl/server';

export default async function manifest(): Promise<MetadataRoute.Manifest> {
  // Pick a locale that is representative of the app
  const locale = 'en';

  const t = await getTranslations({
    namespace: 'Manifest',
    locale
  });

  return {
    name: t('name'),
    start_url: '/',
    theme_color: '#101E33'
  };
}
```

### Sitemap

If you're using a sitemap to inform search engines about all pages of your site, you can attach [locale-specific alternate entries](https://developers.google.com/search/docs/specialty/international/localized-versions#sitemap) to every URL in the sitemap to indicate that a particular page is available in multiple languages or regions.

Note that by default, `next-intl` returns [the `link` response header](/docs/routing/middleware#alternate-links) to instruct search engines that a page is available in multiple languages. While this sufficiently links localized pages for search engines, you may choose to provide this information in a sitemap in case you have more specific requirements.

Next.js supports providing alternate URLs per language via the [`alternates` entry](https://nextjs.org/docs/app/api-reference/file-conventions/metadata/sitemap#generate-a-localized-sitemap) as of version 14.2. You can use your default locale for the main URL and provide alternate URLs based on all locales that your app supports. Keep in mind that also the default locale should be included in the `alternates` object.

<Tabs items={['Shared pathnames', 'Localized pathnames']}>
<Tab>

If you're using the same pathnames for all locales (i.e. you're not using the [`pathnames`](/docs/routing#pathnames) setting), you can assemble the sitemap based on the pathnames that your app supports.

```tsx
import {MetadataRoute} from 'next';

// Can be imported from shared config
const defaultLocale = 'en' as const;
const locales = ['en', 'de'] as const;

// Adapt this as necessary
const host = 'https://acme.com';

export default function sitemap(): MetadataRoute.Sitemap {
  // Adapt this as necessary
  return [
    getEntry('/'),
    getEntry('/users'),
    getEntry('/users/1'),
    getEntry('/users/2')
  ];
}

function getEntry(pathname: string) {
  return {
    url: getUrl(pathname, defaultLocale),
    lastModified: new Date(),
    alternates: {
      languages: Object.fromEntries(
        locales.map((locale) => [locale, getUrl(pathname, locale)])
      )
    }
  };
}

function getUrl(pathname: string, locale: string) {
  return `${host}/${locale}${pathname === '/' ? '' : pathname}`;
}
```

</Tab>
<Tab>

If you're using the [`pathnames`](/docs/routing#pathnames) setting, you can generate sitemap entries for each locale by using the [`getPathname`](/docs/routing/navigation#getpathname) function.

```tsx
import {MetadataRoute} from 'next';
import {locales, defaultLocale} from '@/config';
import {getPathname} from '@/navigation';

// Adapt this as necessary
const host = 'https://acme.com';

export default function sitemap(): MetadataRoute.Sitemap {
  // Adapt this as necessary
  return [
    getEntry('/'),
    getEntry('/users'),
    getEntry({
      pathname: '/users/[id]',
      params: {id: '1'}
    }),
    getEntry({
      pathname: '/users/[id]',
      params: {id: '2'}
    })
  ];
}

type Href = Parameters<typeof getPathname>[0]['href'];

function getEntry(href: Href) {
  return {
    url: getUrl(href, defaultLocale),
    alternates: {
      languages: Object.fromEntries(
        locales.map((locale) => [locale, getUrl(href, locale)])
      )
    }
  };
}

function getUrl(href: Href, locale: (typeof locales)[number]) {
  const pathname = getPathname({locale, href});
  return `${host}/${locale}${pathname === '/' ? '' : pathname}`;
}
```

([working implementation](https://github.com/amannn/next-intl/blob/main/examples/example-app-router/src/app/sitemap.ts))

</Tab>
</Tabs>

<Callout>
  Note that your implementation may vary depending on your routing configuration
  (e.g. if you're using a [`localePrefix`](/docs/routing#locale-prefix) other
  than `always` or [locale-specific domains](/docs/routing#domains)).
</Callout>

### Route Handlers

You can use `next-intl` in [Route Handlers](https://nextjs.org/docs/app/building-your-application/routing/router-handlers) too. The `locale` can either be received from a search param, a layout segment or by parsing the `accept-language` header of the request.

```tsx filename="app/api/hello/route.tsx"
import {NextResponse} from 'next/server';
import {getTranslations} from 'next-intl/server';

export async function GET(request) {
  // Example: Receive the `locale` via a search param
  const {searchParams} = new URL(request.url);
  const locale = searchParams.get('locale');

  const t = await getTranslations({locale, namespace: 'Hello'});
  return NextResponse.json({title: t('title')});
}
```
